﻿using SharpDX.Direct3D11;
using System.Windows.Forms;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Windows;
using Device = SharpDX.Direct3D11.Device;
using Resource = SharpDX.Direct3D11.Resource;
using System.Runtime.InteropServices;
using SharpDX.Direct3D;

namespace Engine
{
    public static class QuadScreen 
    {
        static ShaderConstants constants;
        static Buffer constantsBuffer;

        static EffectContainer EEEM;
        static MeshContainer _MConteints;
        //_3 — half screen
        static RenderTargetView BufRTV_1, BufRTV_2, BufRTV_3, RTV1x1;
        static Texture2D BufTexture_1, BufTexture_2, BufTexture_3, T1x1;
        static ShaderResourceView BufSRV_1, BufSRV_2, BufSRV_3, SRV1x1;
        static SamplerState BufSS;

        static Viewport HDRViewport, DoubleHDRViewport, V1x1;


        public static void FreeResource()
        {
            constantsBuffer.Dispose();
            BufRTV_1.Dispose();
            BufRTV_2.Dispose();
            BufTexture_1.Dispose();
            BufTexture_2.Dispose();
            BufSRV_1.Dispose();
            BufSRV_2.Dispose();
            BufSS.Dispose();
            _MConteints.Dispose();
            BufRTV_3.Dispose();
            BufTexture_3.Dispose();
            BufSRV_3.Dispose();
            T1x1.Dispose();
            SRV1x1.Dispose();
            RTV1x1.Dispose();
         }


        public static void Init(Device device, int W, int H)
        {
            #region Mesh
            _MConteints = new MeshContainer();

            _MConteints.BytesPerVertex = 12;
            _MConteints.FaceCount = 2;
            _MConteints.VertexsCount = 4;

            var vertices = new DataStream(_MConteints.BytesPerVertex * _MConteints.VertexsCount, true, true);
            vertices.Write(new Vector3(0.0f, 0.0f, 0.5f));
            vertices.Write(new Vector3(1.0f, 0.0f, 0.5f));
            vertices.Write(new Vector3(1.0f, 1.0f, 0.5f));
            vertices.Write(new Vector3(0.0f, 1.0f, 0.5f));
            vertices.Position = 0;

            _MConteints.Vertexs = new Buffer(device, vertices, _MConteints.BytesPerVertex * _MConteints.VertexsCount, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);

            var indices = new DataStream(4 * _MConteints.FaceCount * 3, true, true);
            indices.Write(0);
            indices.Write(1);
            indices.Write(2);
            indices.Write(2);
            indices.Write(3);
            indices.Write(0);
            indices.Position = 0;

            _MConteints.Indices = new Buffer(device, indices, 4 * _MConteints.FaceCount * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
            _MConteints.binding = new VertexBufferBinding(_MConteints.Vertexs, _MConteints.BytesPerVertex, 0);

            InputElement[] elements11 = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0) };
            EEEM = ContentManager.LoadEffect("Content/Shaders/QuadShader", elements11);

            // Подготовка константного буффера
            BufferDescription bd = new BufferDescription();
            bd.SizeInBytes = Marshal.SizeOf(typeof(ShaderConstants));
            bd.Usage = ResourceUsage.Dynamic;
            bd.BindFlags = BindFlags.ConstantBuffer;
            bd.CpuAccessFlags = CpuAccessFlags.Write;
            bd.OptionFlags = ResourceOptionFlags.None;
            bd.StructureByteStride = 0;

            constantsBuffer = new Buffer(device, bd);
            constants = new ShaderConstants();
            constants.Resolution = new Vector4(W, H, 0, 0);
            constants.WM = Matrix.OrthoLH(constants.Resolution.X, constants.Resolution.Y, 0.1f, 1000);
            #endregion
            //RenderTargets
            Texture2DDescription BufDescription = new Texture2DDescription();
            BufDescription.MipLevels = 1;
            BufDescription.ArraySize = 1;
            BufDescription.Width = W/4;
            BufDescription.Height = H/4;
            BufDescription.Format = Format.R8G8B8A8_UNorm;
            BufDescription.SampleDescription = new SampleDescription(1, 0);
            BufDescription.Usage = ResourceUsage.Default;
            BufDescription.BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource;
            BufDescription.CpuAccessFlags = CpuAccessFlags.None;
            BufDescription.OptionFlags = ResourceOptionFlags.None;

            BufTexture_1 = new Texture2D(device, BufDescription);
            BufTexture_2 = new Texture2D(device, BufDescription);

            BufSRV_1 = new ShaderResourceView(device, BufTexture_1);
            BufSRV_2 = new ShaderResourceView(device, BufTexture_2);

            RenderTargetViewDescription RTVD = new RenderTargetViewDescription();
            RTVD.Format = BufDescription.Format;
            RTVD.Dimension = RenderTargetViewDimension.Texture2D;
            RTVD.Texture2DArray.FirstArraySlice = 0;
            RTVD.Texture2DArray.ArraySize = 1;
            RTVD.Texture2D.MipSlice = 0;

            BufRTV_1 = new RenderTargetView(device, BufTexture_1, RTVD);
            BufRTV_2 = new RenderTargetView(device, BufTexture_2, RTVD);

            BufDescription.Width = W / 2;
            BufDescription.Height = H / 2;
            BufDescription.MipLevels = 0;

            BufDescription.OptionFlags = ResourceOptionFlags.GenerateMipMaps;
            BufDescription.Format = Format.R32G32B32A32_Float;
            RTVD.Format = BufDescription.Format;
            BufTexture_3 = new Texture2D(device, BufDescription);
            BufSRV_3 = new ShaderResourceView(device, BufTexture_3);
            BufRTV_3 = new RenderTargetView(device, BufTexture_3, RTVD);


            SamplerStateDescription sd = new SamplerStateDescription();
            sd.AddressU = TextureAddressMode.Clamp;
            sd.AddressV = TextureAddressMode.Clamp;
            sd.AddressW = TextureAddressMode.Clamp;
            sd.Filter = Filter.MinMagMipLinear;
            sd.MinimumLod = 0;
            sd.MaximumLod = 16;

            BufSS = new SamplerState(device, ref sd);

            HDRViewport = new Viewport(0, 0, W / 4, H / 4);
            DoubleHDRViewport = new Viewport(0, 0, W / 2, H / 2);

            BufDescription = new Texture2DDescription();
            BufDescription.MipLevels = 1;
            BufDescription.ArraySize = 1;
            BufDescription.Width = 1;
            BufDescription.Height = 1;
            BufDescription.Format = Format.R32G32B32A32_Float;
            BufDescription.SampleDescription = new SampleDescription(1, 0);
            BufDescription.Usage = ResourceUsage.Default;
            BufDescription.BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource;
            BufDescription.CpuAccessFlags = CpuAccessFlags.None;
            BufDescription.OptionFlags = ResourceOptionFlags.None;

            RTVD = new RenderTargetViewDescription();
            RTVD.Format = BufDescription.Format;
            RTVD.Dimension = RenderTargetViewDimension.Texture2D;
            RTVD.Texture2DArray.FirstArraySlice = 0;
            RTVD.Texture2DArray.ArraySize = 1;
            RTVD.Texture2D.MipSlice = 0;

            T1x1 = new Texture2D(device, BufDescription);
            V1x1 = new Viewport(0, 0, 1, 1);
            SRV1x1 = new ShaderResourceView(device, T1x1);
            RTV1x1 = new RenderTargetView(device, T1x1, RTVD);

            device.ImmediateContext.ClearRenderTargetView(BufRTV_3, new Color4(0.0f, 0.0f, 0.0f, 1f));

        }

        public static void Render(Device device, DeviceContext context, ShaderResourceView resourceView, SamplerState SSDiffuse, bool InvertUV)
        {
            context.InputAssembler.InputLayout = EEEM.layout;
            context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            context.InputAssembler.SetVertexBuffers(0, _MConteints.binding);
            
            context.InputAssembler.SetIndexBuffer(_MConteints.Indices, Format.R32_UInt, 0);

            //renderTechnique.GetPassByIndex(0).Apply(context);
            if (!InvertUV)
                EEEM.renderTechniques[0].GetPassByIndex(0).Apply(context);
            else
                EEEM.renderTechniques[0].GetPassByIndex(1).Apply(context);

            context.PixelShader.SetShaderResource(0, resourceView);
            context.PixelShader.SetSampler(0, SSDiffuse);


            var dataBox = ModelViewer.Program.context.MapSubresource(constantsBuffer, 0, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None);
            Utilities.Write(dataBox.DataPointer, ref constants);
            ModelViewer.Program.context.UnmapSubresource(constantsBuffer, 0);

            //Константный буффер передается шейдерам
            device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
            device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);

            context.DrawIndexed(6, 0, 0);
        }

        public static void FinalRender(Device device, float TimeMili, DeviceContext context, ShaderResourceView DiffuseHDR, ShaderResourceView DepthMap, RenderTargetView renderTarget)
        {
            constants.Time = new Vector4(TimeMili, TimeMili, TimeMili, TimeMili)/1000f;

            ViewportF[] pre = context.Rasterizer.GetViewports();
            context.InputAssembler.InputLayout = EEEM.layout;
            context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            context.InputAssembler.SetVertexBuffers(0, _MConteints.binding);
            context.InputAssembler.SetIndexBuffer(_MConteints.Indices, Format.R32_UInt, 0);


            var dataBox = context.MapSubresource(constantsBuffer, 0, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None);
            Utilities.Write(dataBox.DataPointer, ref constants);
            context.UnmapSubresource(constantsBuffer, 0);

            const bool boolHDR = true;
            if (boolHDR)
            {
                context.Rasterizer.SetViewports(DoubleHDRViewport);
                context.OutputMerger.SetTargets(BufRTV_3);

                EEEM.renderTechniques[5].GetPassByIndex(0).Apply(context);
                context.PixelShader.SetShaderResource(0, DiffuseHDR);
                context.PixelShader.SetSampler(0, BufSS);
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
                context.DrawIndexed(6, 0, 0);

                device.ImmediateContext.GenerateMips(BufSRV_3);
                
                ///////////////////////////////////////////
                //lerp
                context.Rasterizer.SetViewports(V1x1);
                context.OutputMerger.SetTargets(RTV1x1);

                EEEM.renderTechniques[7].GetPassByIndex(0).Apply(context);
                context.PixelShader.SetShaderResource(0, BufSRV_3);
                context.PixelShader.SetSampler(0, BufSS);
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
                context.DrawIndexed(6, 0, 0);
                //////////////////////////////////////////


                context.Rasterizer.SetViewports(HDRViewport);
                context.OutputMerger.SetTargets(BufRTV_1);
                EEEM.renderTechniques[6].GetPassByIndex(0).Apply(context);
                context.PixelShader.SetShaderResource(0, BufSRV_3);
                context.PixelShader.SetSampler(0, BufSS);
                context.PixelShader.SetShaderResource(2, SRV1x1);
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
                context.DrawIndexed(6, 0, 0);

                //////////////////////////////////////////////////////////////////////////

                context.Rasterizer.SetViewports(HDRViewport);
                context.OutputMerger.SetTargets(BufRTV_1);
                //context.ClearRenderTargetView(HDRTarget, new Color4(0.5f, 0.5f, 1.0f));
                /*
                EEEM.renderTechniques[2].GetPassByIndex(0).Apply(context);
                context.PixelShader.SetShaderResource(0, BufSRV_2);
                context.PixelShader.SetSampler(0, BufSS);
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
                context.DrawIndexed(6, 0, 0);
                */
                
                
                context.OutputMerger.SetTargets(BufRTV_2);
                EEEM.renderTechniques[3].GetPassByIndex(0).Apply(context);
                context.PixelShader.SetShaderResource(0, BufSRV_1);
                context.PixelShader.SetSampler(0, BufSS);
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
                context.DrawIndexed(6, 0, 0);

                context.OutputMerger.SetTargets(BufRTV_1);
                EEEM.renderTechniques[4].GetPassByIndex(0).Apply(context);
                context.PixelShader.SetShaderResource(0, BufSRV_2);
                context.PixelShader.SetSampler(0, BufSS);
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
                context.DrawIndexed(6, 0, 0);
                 

            }
            context.Rasterizer.SetViewports(pre);
            context.OutputMerger.SetTargets(renderTarget);
            EEEM.renderTechniques[1].GetPassByIndex(0).Apply(context);
            context.PixelShader.SetShaderResource(0, DiffuseHDR);
            context.PixelShader.SetSampler(0, BufSS);
            context.PixelShader.SetShaderResource(1, BufSRV_1);
            context.PixelShader.SetShaderResource(2, SRV1x1);
            context.PixelShader.SetShaderResource(3, DepthMap);


           // context.PixelShader.SetSampler(0, BufSS);

            device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
            device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
            context.DrawIndexed(6, 0, 0);
            
        }
        
        private struct ShaderConstants
        {
            public Matrix WM;
            public Vector4 Resolution;
            public Vector4 xyXY;
            public Vector4 Color_Symbol;
            public Vector4 Time;

        }

    }
}
